Skip to content

Binder: companion objects as values; proxy identity equality (#322) - #324

Merged
b-re-w merged 1 commit into
developfrom
feat/binder-companion-value
Oct 25, 2026
Merged

b-re-w merged 1 commit into
developfrom
feat/binder-companion-value

Conversation

@b-re-w

@b-re-w b-re-w commented Oct 25, 2026

Copy link
Copy Markdown
Member

Closes #322

The approved design, with the leader's decisions on equality (a) and register_empty:

  • Walker: binds Owner. as a STATIC_GETTER. The generated Kotlin source is Owner.Companion, which kotlinc compiles to GETSTATIC, so there is no name lookup and no Compose name. The returnTypeName is Owner when the companion's metadata lists Owner as a direct supertype, otherwise the companion's own type. The candidate is exempt from the 'constructor collides with a constant under its own name' rule (ArtifactScanner.kt:345).
  • Runtime: a proxy class falls back to its same-named module's statics, so Modifier.Companion and other class-level constants work through the class.
  • Found in verification and fixed: PythonProxySource bound the new androidx.compose.ui.Modifier module over the Modifier class on its parent package, so 'from androidx.compose.ui import Modifier' returned a module. Now one Python name is one thing: the class wins, as in PythonProxySource's own rule, unless a declaration of that name is bound in the parent. That exception keeps Binder: a companion-object module shadows a same-named factory function (TextRange(2)) #78's callable module: TextRange(2) beside TextRange.Zero, caught by TextFieldStateRenderTest. The rule is applied in the adapter at install time and in PythonProxySource._pm_module, so it holds in both install orders.
  • Equality, option (a):
    • Proxies are == when they hold the same Kotlin object; hash is the identity hash.
    • is is not guaranteed, and Kotlin equals and hashCode are not used. SPEC U-4 says so.
    • Only live int handles reach Kotlin, and equal handles answer True without a call.
    • wasm has no identity hash and uses hashCode; SPEC notes this.
  • register_empty also takes the proxy class and a Python callable. The existing string form still works, and both are tested.
  • Stubs: the handle is rendered as Companion: Owner inside the owner's class stub, with a marker the stub test counts.

Tests:

  • CompanionHandleBindingTest covers interface, named, non-subtype, non-public and generic companions, and that the constructor is still bound.
  • CompanionHandleStubTest, CompanionHandleTest (== and hash; two Kotlin-equal distinct objects are not ==; both register_empty forms).
  • WalkedArtifactCompanionHandleTest on the real Compose jars: Modifier.Companion is a Modifier, two reads are ==, and it is Compose's empty Modifier.
  • ProxyClassArgumentRenderTest.aModifierClassPassedToCardIsTheCompanionRegisteredFromPython: register_empty(Modifier, lambda: Modifier.Companion), then Card(modifier=Modifier) renders the same as Card(...).

Mutants:

  • With the constructor exemption off, theOwnersConstructorIsStillBoundBesideItsCompanionHandle fails.
  • With the companion candidate off, 8 plugin tests and the compose render test fail.

Results: plugin 350/350, artifact fixture 46/46, python-multiplatform desktopTest 643/643, compose fixture 129/129, ksp app fixture 79/79. androidNative, iOS simulator, wasmJs and Android debug compile pass.

… compare by Kotlin object identity (#322)

The walker binds Owner.<Companion> as a STATIC_GETTER whose generated Kotlin names the companion
(kotlinc emits GETSTATIC), typed as Owner when the companion directly implements it, and exempt from
the rule that drops a constructor beside an Owner.x key. A proxy class falls back to its same-named
module's statics, and one Python name stays one thing: the proxy class wins over a module of the same
name unless a declaration of that name is bound in the parent (#78's callable module), in the adapter
and in PythonProxySource. Proxies are == when they hold the same Kotlin object (identity hash; is not
guaranteed; Kotlin equals is not used). register_empty also takes the proxy class and a Python
callable. Stubs render the handle as a class attribute.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant